<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JWT Authentication with Automatic Refresh Token Management</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .container {
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            padding: 40px;
            max-width: 900px;
            width: 100%;
            position: relative;
            overflow: hidden;
            animation: slideIn 0.6s ease-out;
        }

        @keyframes slideIn {
            from { opacity: 0; transform: translateY(30px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .container::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 4px;
            background: linear-gradient(90deg, #667eea, #764ba2);
        }

        .header {
            text-align: center;
            margin-bottom: 30px;
        }

        .auth-logo {
            font-size: 48px;
            margin-bottom: 20px;
            color: #333;
        }

        h1 {
            color: #333;
            margin-bottom: 10px;
            font-size: 28px;
            font-weight: 700;
        }

        .subtitle {
            color: #666;
            margin-bottom: 30px;
            font-size: 16px;
        }

        .section {
            margin-bottom: 30px;
            padding: 25px;
            background: rgba(255, 255, 255, 0.7);
            border-radius: 15px;
            border: 1px solid rgba(255, 255, 255, 0.3);
            transition: all 0.3s ease;
        }

        .section:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
        }

        .section h2 {
            color: #333;
            margin-bottom: 15px;
            font-size: 20px;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .section p {
            color: #666;
            margin-bottom: 20px;
            font-size: 14px;
        }

        /* Token Timer Styles */
        .token-timer {
            display: none;
            margin-top: 20px;
            padding: 20px;
            background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
            border-radius: 15px;
            border: 2px solid rgba(102, 126, 234, 0.3);
            text-align: center;
            transition: all 0.3s ease;
        }

        .token-timer.active {
            display: block;
            animation: fadeIn 0.5s ease;
        }

        .token-timer.warning {
            background: linear-gradient(135deg, rgba(255, 193, 7, 0.1), rgba(255, 152, 0, 0.1));
            border-color: rgba(255, 193, 7, 0.5);
        }

        .token-timer.expired {
            background: linear-gradient(135deg, rgba(220, 53, 69, 0.1), rgba(255, 107, 107, 0.1));
            border-color: rgba(220, 53, 69, 0.5);
        }

        .timer-icon {
            font-size: 36px;
            margin-bottom: 10px;
        }

        .timer-label {
            font-size: 14px;
            color: #666;
            margin-bottom: 8px;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        .timer-display {
            font-size: 32px;
            font-weight: bold;
            color: #333;
            font-family: 'SF Mono', Monaco, monospace;
            margin-bottom: 10px;
        }

        .timer-display.warning {
            color: #ff9800;
            animation: pulse 1s infinite;
        }

        .timer-display.expired {
            color: #dc3545;
        }

        @keyframes pulse {
            0% { opacity: 1; transform: scale(1); }
            50% { opacity: 0.8; transform: scale(1.05); }
            100% { opacity: 1; transform: scale(1); }
        }

        .timer-details {
            font-size: 12px;
            color: #666;
            margin-top: 10px;
        }

        .timer-progress {
            width: 100%;
            height: 8px;
            background: rgba(0, 0, 0, 0.1);
            border-radius: 4px;
            overflow: hidden;
            margin-top: 15px;
        }

        .timer-progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #667eea, #764ba2);
            border-radius: 4px;
            transition: width 0.3s ease, background 0.3s ease;
        }

        .timer-progress-bar.warning {
            background: linear-gradient(90deg, #ff9800, #ff6b00);
        }

        .timer-progress-bar.expired {
            background: linear-gradient(90deg, #dc3545, #c82333);
        }

        /* User Selection Section */
        .user-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 15px;
            margin-bottom: 25px;
        }

        .user-card {
            background: rgba(255, 255, 255, 0.9);
            padding: 18px;
            border-radius: 12px;
            border: 2px solid rgba(255, 255, 255, 0.5);
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
            min-width: 0;
        }

        .user-card::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.1), transparent);
            transition: left 0.5s;
        }

        .user-card:hover::before {
            left: 100%;
        }

        .user-card:hover {
            transform: translateY(-3px);
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
            border-color: #667eea;
            background: rgba(102, 126, 234, 0.05);
        }

        .user-card.active {
            border-color: #667eea;
            background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.2);
        }

        .user-card.active::after {
            content: '✔';
            position: absolute;
            top: 10px;
            right: 10px;
            width: 24px;
            height: 24px;
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 14px;
            font-weight: bold;
            animation: checkIn 0.3s ease-out;
        }

        @keyframes checkIn {
            from {
                transform: scale(0) rotate(-180deg);
                opacity: 0;
            }
            to {
                transform: scale(1) rotate(0);
                opacity: 1;
            }
        }

        .user-card-icon {
            font-size: 28px;
            margin-bottom: 8px;
            text-align: center;
        }

        .user-card-name {
            font-weight: 600;
            color: #333;
            font-size: 15px;
            margin-bottom: 4px;
        }

        .user-card-credentials {
            font-family: 'SF Mono', Monaco, monospace;
            font-size: 12px;
            color: #666;
            margin-bottom: 6px;
        }

        .user-card-password {
            font-size: 11px;
            color: #888;
            font-style: italic;
        }

        .token-display {
            background: #f8f9fa;
            border: 2px dashed #dee2e6;
            border-radius: 10px;
            padding: 15px;
            margin-top: 15px;
            font-family: 'SF Mono', Monaco, monospace;
            font-size: 12px;
            word-break: break-all;
            color: #495057;
            min-height: 60px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.3s ease;
        }

        .token-display.has-token {
            background: rgba(40, 167, 69, 0.05);
            border-color: rgba(40, 167, 69, 0.3);
            color: #155724;
            display: block;
        }

        .jwt-decoded {
            display: none;
            margin-top: 20px;
            padding: 20px;
            background: rgba(255, 255, 255, 0.9);
            border-radius: 10px;
            border: 2px solid rgba(102, 126, 234, 0.2);
        }

        .jwt-decoded.show {
            display: block;
            animation: fadeIn 0.5s ease;
        }

        .jwt-part {
            margin-bottom: 20px;
        }

        .jwt-part-header {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-bottom: 10px;
            font-weight: 600;
            color: #333;
            font-size: 14px;
        }

        .jwt-part-label {
            display: inline-block;
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            padding: 3px 8px;
            border-radius: 6px;
            font-size: 11px;
            font-weight: 600;
            text-transform: uppercase;
        }

        .jwt-part-content {
            background: #f8f9fa;
            border: 1px solid #e9ecef;
            border-radius: 8px;
            padding: 12px;
            font-family: 'SF Mono', Monaco, monospace;
            font-size: 12px;
            overflow-x: auto;
        }

        .jwt-json {
            white-space: pre-wrap;
            word-wrap: break-word;
        }

        .jwt-raw {
            color: #6c757d;
            font-size: 10px;
            word-break: break-all;
            margin-top: 8px;
            padding-top: 8px;
            border-top: 1px dashed #dee2e6;
        }

        .jwt-field {
            margin: 4px 0;
        }

        .jwt-field-name {
            color: #6f42c1;
            font-weight: bold;
        }

        .jwt-field-value {
            color: #495057;
        }

        .jwt-field-timestamp {
            color: #28a745;
            font-size: 11px;
            margin-left: 8px;
        }

        .form-group {
            margin-bottom: 20px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: #333;
            font-size: 14px;
        }

        input[type="text"], input[type="password"] {
            width: 100%;
            padding: 12px 16px;
            border: 2px solid #e1e5e9;
            border-radius: 10px;
            font-size: 16px;
            transition: all 0.3s ease;
            background: white;
        }

        input[type="text"]:focus, input[type="password"]:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
        }

        .input-wrapper {
            position: relative;
        }

        .input-icon {
            position: absolute;
            right: 15px;
            top: 50%;
            transform: translateY(-50%);
            color: #999;
            font-size: 18px;
        }

        .btn {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 12px 24px;
            border: none;
            border-radius: 10px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-right: 10px;
            margin-bottom: 10px;
            position: relative;
            overflow: hidden;
        }

        .btn::before {
            content: '';
            position: absolute;
            top: 0;
            left: -100%;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
            transition: left 0.5s;
        }

        .btn:hover::before {
            left: 100%;
        }

        .btn-primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }

        .btn-secondary {
            background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
            color: #333;
        }

        .btn-success {
            background: linear-gradient(135deg, #4CAF50, #45a049);
            color: white;
        }

        .btn-danger {
            background: linear-gradient(135deg, #FF6B6B, #ee5a52);
            color: white;
        }

        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
        }

        .btn:disabled {
            opacity: 0.6;
            cursor: not-allowed;
            transform: none !important;
        }

        .response {
            margin-top: 20px;
            padding: 20px;
            border-radius: 10px;
            font-family: 'SF Mono', Monaco, monospace;
            font-size: 14px;
            line-height: 1.5;
            white-space: pre-wrap;
            word-wrap: break-word;
            max-height: 200px;
            overflow-y: auto;
            display: none;
            transition: all 0.3s ease;
            position: relative;
        }

        .response.show {
            display: block;
            animation: fadeIn 0.5s ease;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .response.success {
            background: rgba(40, 167, 69, 0.1);
            border: 1px solid rgba(40, 167, 69, 0.3);
            color: #155724;
        }

        .response.error {
            background: rgba(220, 53, 69, 0.1);
            border: 1px solid rgba(220, 53, 69, 0.3);
            color: #721c24;
        }

        .response-header {
            position: absolute;
            top: -10px;
            left: 20px;
            background: white;
            padding: 0 10px;
            font-size: 12px;
            font-weight: bold;
            color: inherit;
        }

        .status-indicator {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            margin-right: 8px;
        }

        .status-indicator.connected {
            background: #28a745;
            animation: pulse 2s infinite;
        }

        .status-indicator.disconnected {
            background: #dc3545;
        }

        .auto-refresh {
            margin-top: 20px;
            padding: 20px;
            background: rgba(255, 193, 7, 0.1);
            border: 1px solid rgba(255, 193, 7, 0.3);
            border-radius: 10px;
            color: #856404;
        }

        .auto-refresh-notice {
            padding: 15px;
            background: rgba(102, 126, 234, 0.1);
            border: 1px solid rgba(102, 126, 234, 0.3);
            border-radius: 10px;
            color: #004085;
            margin-top: 15px;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .auto-refresh-icon {
            font-size: 20px;
            animation: spin 2s linear infinite;
        }

        @keyframes spin {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }

        .countdown {
            font-weight: bold;
            color: #dc3545;
        }

        .transaction-details {
            display: none;
            margin-top: 20px;
        }

        .transaction-details.visible {
            display: block;
            animation: fadeIn 0.5s ease-out;
        }

        .http-message {
            background: #f8f9fa;
            border: 1px solid #e9ecef;
            border-radius: 10px;
            margin-bottom: 15px;
            overflow: hidden;
        }

        .http-message-header {
            background: linear-gradient(45deg, #495057, #6c757d);
            color: white;
            padding: 12px 20px;
            font-weight: 600;
            font-size: 14px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .http-message-content {
            padding: 20px;
            font-family: 'SF Mono', Monaco, monospace;
            font-size: 13px;
            line-height: 1.6;
            background: white;
        }

        .http-message-content pre {
            white-space: pre-wrap;
            word-wrap: break-word;
            word-break: break-all;
            overflow-wrap: break-word;
            margin: 0;
            font-family: inherit;
            font-size: inherit;
        }

        .http-request {
            border-left: 4px solid #007bff;
        }

        .http-request .http-message-header {
            background: linear-gradient(45deg, #007bff, #0056b3);
        }

        .http-response-200 {
            border-left: 4px solid #28a745;
        }

        .http-response-200 .http-message-header {
            background: linear-gradient(45deg, #28a745, #1e7e34);
        }

        .http-response-401 {
            border-left: 4px solid #ffc107;
        }

        .http-response-401 .http-message-header {
            background: linear-gradient(45deg, #ffc107, #e0a800);
        }

        .http-method { color: #007bff; font-weight: bold; }
        .http-status-200 { color: #28a745; font-weight: bold; }
        .http-status-401 { color: #ffc107; font-weight: bold; }
        .http-header-name { color: #6f42c1; font-weight: bold; }
        .http-header-value { color: #495057; }

        .transaction-flow {
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 20px 0;
            font-size: 24px;
            color: #6c757d;
        }

        .transaction-step {
            padding: 15px;
            background: rgba(255, 193, 7, 0.1);
            border: 1px solid rgba(255, 193, 7, 0.3);
            border-radius: 10px;
            margin: 15px 0;
            color: #856404;
            font-size: 14px;
            font-weight: 600;
            text-align: center;
        }

        .footer {
            margin-top: 30px;
            padding-top: 20px;
            border-top: 1px solid rgba(255, 255, 255, 0.3);
            color: #666;
            font-size: 14px;
            text-align: center;
        }

        @media (max-width: 768px) {
            .container {
                padding: 20px;
                margin: 10px;
            }

            .section {
                padding: 20px;
            }

            .user-grid {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
<div class="container">
    <div class="header">
        <div class="auth-logo">🔐</div>
        <h1>JWT Authentication Demo</h1>
        <p class="subtitle">JWT with Automatic Refresh Token Management</p>
    </div>

    <!-- Authentication Section -->
    <div class="section">
        <h2><span class="status-indicator" id="authStatus"></span>Authentication</h2>
        <p>Click on any user card below to automatically fill in the credentials</p>

        <div class="user-grid" id="userGrid">
            <div class="user-card" data-username="john" data-password="password123">
                <div class="user-card-icon">👤</div>
                <div class="user-card-name">John</div>
                <div class="user-card-credentials">Username: john</div>
                <div class="user-card-password">Password: password123</div>
            </div>
            <div class="user-card" data-username="jane" data-password="secret456">
                <div class="user-card-icon">👤</div>
                <div class="user-card-name">Jane</div>
                <div class="user-card-credentials">Username: jane</div>
                <div class="user-card-password">Password: secret456</div>
            </div>
            <div class="user-card" data-username="admin" data-password="admin123">
                <div class="user-card-icon">👨‍💼</div>
                <div class="user-card-name">Admin</div>
                <div class="user-card-credentials">Username: admin</div>
                <div class="user-card-password">Password: admin123</div>
            </div>
        </div>

        <div id="loginForm">
            <div class="form-group">
                <label for="username">Username:</label>
                <div class="input-wrapper">
                    <input type="text" id="username" placeholder="Enter username or click a user card above">
                    <span class="input-icon">👤</span>
                </div>
            </div>
            <div class="form-group">
                <label for="password">Password:</label>
                <div class="input-wrapper">
                    <input type="password" id="password" placeholder="Enter password or click a user card above">
                    <span class="input-icon">🔒</span>
                </div>
            </div>
            <button class="btn btn-primary" onclick="login()">
                <span>🔑</span>
                Login & Get JWT Token
            </button>
            <button class="btn btn-secondary" onclick="clearTokens()">
                <span>🗑️</span>
                Clear Tokens
            </button>
            <button class="btn btn-secondary" onclick="revokeRefreshToken()">
                <span>⏰</span>
                Logout (Revoke Refresh Token)
            </button>
        </div>

        <div id="authResponse" class="response">
            <span class="response-header">Response</span>
        </div>

        <!-- Token Timer Display -->
        <div id="tokenTimer" class="token-timer">
            <div class="timer-icon" id="timerIcon">⏱️</div>
            <div class="timer-label">Token Expires In</div>
            <div class="timer-display" id="timerDisplay">--:--</div>
            <div class="timer-details" id="timerDetails">
                Expires at: <span id="expiryTime">--:--:--</span>
            </div>
            <div class="timer-progress">
                <div class="timer-progress-bar" id="timerProgressBar"></div>
            </div>
        </div>

        <div class="token-display" id="tokenDisplay">
            <span style="color: #999;">No JWT token yet. Login to receive a token.</span>
        </div>

        <div class="auto-refresh-notice">
            <span class="auto-refresh-icon">🔄</span>
            <span><strong>Automatic Token Refresh Enabled:</strong> When your access token expires, the client will automatically use your refresh token to get new tokens and retry the request. You don't need to manually refresh!</span>
        </div>

        <div class="jwt-decoded" id="jwtDecoded">
            <h3 style="margin-bottom: 20px; color: #333; font-size: 18px;">🔍 Decoded JWT Token</h3>

            <div class="jwt-part">
                <div class="jwt-part-header">
                    <span class="jwt-part-label">HEADER</span>
                    <span>Algorithm & Token Type</span>
                </div>
                <div class="jwt-part-content">
                    <div class="jwt-json" id="jwtHeader"></div>
                    <div class="jwt-raw" id="jwtHeaderRaw"></div>
                </div>
            </div>

            <div class="jwt-part">
                <div class="jwt-part-header">
                    <span class="jwt-part-label">PAYLOAD</span>
                    <span>Data & Claims</span>
                </div>
                <div class="jwt-part-content">
                    <div class="jwt-json" id="jwtPayload"></div>
                    <div class="jwt-raw" id="jwtPayloadRaw"></div>
                </div>
            </div>

            <div class="jwt-part">
                <div class="jwt-part-header">
                    <span class="jwt-part-label">SIGNATURE</span>
                    <span>Verification</span>
                </div>
                <div class="jwt-part-content">
                    <div style="color: #6c757d; font-size: 12px;">
                        <strong>Signature (Base64Url Encoded):</strong><br>
                        <span id="jwtSignature" style="word-break: break-all; color: #495057;"></span>
                        <div style="margin-top: 10px; padding: 10px; background: #fff3cd; border-radius: 6px; border: 1px solid #ffeeba;">
                            <strong style="color: #856404;">⚠️ Note:</strong> The signature is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn't changed along the way.
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Protected Route Section -->
    <div class="section">
        <h2>🛡️ Protected Route Access</h2>
        <p>These endpoints require JWT authentication. Access tokens will be automatically refreshed when expired!</p>
        <button class="btn btn-success" onclick="accessProtectedRoute()" id="protectedBtn">
            <span>👤</span>
            Access Profile
        </button>
        <button class="btn btn-danger" onclick="accessAdminRoute()" id="protectedAdminBtn">
            <span>👨‍💼</span>
            Admin: List Users
        </button>
        <button class="btn btn-secondary" onclick="refreshTokensManually()" id="refreshBtn">
            <span>🔄</span>
            Manual Token Refresh
        </button>
        <div id="protectedResponse" class="response">
            <span class="response-header">Response</span>
        </div>
    </div>

    <!-- HTTP Transaction Details Section -->
    <div class="section">
        <h2>📊 HTTP Transaction Details</h2>
        <p>View detailed HTTP request and response messages (including automatic refresh flow)</p>
        <button class="btn btn-secondary" onclick="clearTransactions()" style="margin-bottom: 15px;">
            <span>🗑️</span>
            Clear Transactions
        </button>
        <div id="transactionDetails" class="transaction-details">
            <p style="text-align: center; color: #6c757d; font-style: italic; padding: 20px;">
                Click any endpoint button to see detailed HTTP request/response messages
            </p>
        </div>
    </div>

    <div class="footer">
        <p>This demo showcases JWT Bearer Token Authentication with automatic token refresh capabilities</p>
    </div>
</div>

<script>
    let accessToken = null;
    let refreshToken = null;
    let tokenExpiryTime = null;
    let initialTokenDuration = null;
    let tokenTimerInterval = null;
    let transactionHistory = [];

    // Token Timer Management
    const TokenTimer = {
        start(expiryTime, initialDuration) {
            this.stop(); // Clear any existing timer

            const timerElement = document.getElementById('tokenTimer');
            const timerDisplay = document.getElementById('timerDisplay');
            const timerDetails = document.getElementById('timerDetails');
            const expiryTimeElement = document.getElementById('expiryTime');
            const progressBar = document.getElementById('timerProgressBar');
            const timerIcon = document.getElementById('timerIcon');

            timerElement.classList.add('active');
            timerElement.classList.remove('warning', 'expired');
            timerDisplay.classList.remove('warning', 'expired');
            progressBar.classList.remove('warning', 'expired');

            // Set expiry time display
            expiryTimeElement.textContent = expiryTime.toLocaleTimeString();

            tokenTimerInterval = setInterval(() => {
                const now = new Date();
                const remainingMs = expiryTime - now;

                if (remainingMs <= 0) {
                    // Token has expired
                    timerDisplay.textContent = 'EXPIRED';
                    timerDisplay.classList.add('expired');
                    timerElement.classList.add('expired');
                    progressBar.classList.add('expired');
                    progressBar.style.width = '0%';
                    timerIcon.textContent = '❌';
                    this.stop();
                    return;
                }

                // Calculate time components
                const totalSeconds = Math.floor(remainingMs / 1000);
                const minutes = Math.floor(totalSeconds / 60);
                const seconds = totalSeconds % 60;

                // Format display
                const displayMinutes = String(minutes).padStart(2, '0');
                const displaySeconds = String(seconds).padStart(2, '0');
                timerDisplay.textContent = `${displayMinutes}:${displaySeconds}`;

                // Calculate progress percentage
                const progressPercent = (remainingMs / (initialDuration * 1000)) * 100;
                progressBar.style.width = `${progressPercent}%`;

                // Warning state when less than 30 seconds remaining
                if (totalSeconds <= 30) {
                    if (!timerElement.classList.contains('warning')) {
                        timerElement.classList.add('warning');
                        timerDisplay.classList.add('warning');
                        progressBar.classList.add('warning');
                        timerIcon.textContent = '⚠️';
                    }
                } else {
                    timerIcon.textContent = '⏱️';
                }
            }, 100); // Update every 100ms for smooth countdown
        },

        stop() {
            if (tokenTimerInterval) {
                clearInterval(tokenTimerInterval);
                tokenTimerInterval = null;
            }
        },

        hide() {
            this.stop();
            const timerElement = document.getElementById('tokenTimer');
            timerElement.classList.remove('active', 'warning', 'expired');
        }
    };

    // Core function that implements automatic token refresh with complete transaction logging
    async function makeAuthenticatedRequest(url, options = {}) {
        // Clear previous transactions for this request flow
        transactionHistory = [];

        const attemptRequest = async (token, isRetry = false) => {
            const headers = {
                ...options.headers,
                'Authorization': `Bearer ${token}`
            };

            const requestDetails = {
                method: options.method || 'GET',
                url: url,
                headers: headers,
                body: options.body || null,
                isRetry: isRetry
            };

            const response = await fetch(url, {
                ...options,
                headers: headers
            });

            const responseText = await response.text();

            // Log this transaction
            transactionHistory.push({
                request: requestDetails,
                response: {
                    status: response.status,
                    statusText: response.statusText,
                    headers: {
                        'Content-Type': response.headers.get('Content-Type') || 'text/plain',
                        'WWW-Authenticate': response.headers.get('WWW-Authenticate') || '',
                        'Content-Length': responseText.length
                    },
                    body: responseText
                }
            });

            return {
                response: response,
                text: responseText
            };
        };

        const refreshAndRetry = async () => {
            console.log('Access token expired, refreshing...');

            const formData = new URLSearchParams();
            formData.append('refreshToken', refreshToken);

            const refreshHeaders = {
                'Content-Type': 'application/x-www-form-urlencoded'
            };

            const refreshResponse = await fetch('/refresh', {
                method: 'POST',
                headers: refreshHeaders,
                body: formData
            });

            const refreshText = await refreshResponse.text();

            // Log the refresh transaction
            transactionHistory.push({
                request: {
                    method: 'POST',
                    url: '/refresh',
                    headers: refreshHeaders,
                    body: formData.toString(),
                    isRefresh: true
                },
                response: {
                    status: refreshResponse.status,
                    statusText: refreshResponse.statusText,
                    headers: {
                        'Content-Type': refreshResponse.headers.get('Content-Type') || 'text/plain',
                        'Content-Length': refreshText.length
                    },
                    body: refreshText
                }
            });

            if (!refreshResponse.ok) {
                throw new Error(`Token refresh failed: ${refreshText}`);
            }

            const tokens = JSON.parse(refreshText);
            accessToken = tokens.accessToken;
            refreshToken = tokens.refreshToken;
            tokenExpiryTime = new Date(Date.now() + (tokens.expiresIn * 1000));
            initialTokenDuration = tokens.expiresIn;
            updateTokenDisplay(tokens);

            // Now retry the original request with the new token
            return await attemptRequest(accessToken, true);
        };

        // Check if we have tokens
        if (!accessToken || !refreshToken) {
            throw new Error('No authentication tokens available. Please login first.');
        }

        // First attempt with current access token
        const result = await attemptRequest(accessToken);

        // If unauthorized, refresh and retry
        if (result.response.status === 401) {
            const retryResult = await refreshAndRetry();
            displayCompleteTransactionFlow();
            return {
                ok: retryResult.response.ok,
                status: retryResult.response.status,
                text: () => Promise.resolve(retryResult.text),
                headers: retryResult.response.headers
            };
        }

        displayCompleteTransactionFlow();
        return {
            ok: result.response.ok,
            status: result.response.status,
            text: () => Promise.resolve(result.text),
            headers: result.response.headers
        };
    }

    // Display complete transaction flow including all steps
    function displayCompleteTransactionFlow() {
        const detailsContainer = document.getElementById('transactionDetails');
        if (!detailsContainer || transactionHistory.length === 0) return;

        let html = '';

        transactionHistory.forEach((transaction, index) => {
            const isRefreshRequest = transaction.request.isRefresh;
            const isRetryRequest = transaction.request.isRetry;
            const responseClass = transaction.response.status === 200 ? 'http-response-200' : 'http-response-401';

            // Add step indicator for multi-step flows
            if (transactionHistory.length > 1) {
                if (index === 0) {
                    html += '<div class="transaction-step">Step 1: Initial Request (with expired token)</div>';
                } else if (isRefreshRequest) {
                    html += '<div class="transaction-step">Step 2: Automatic Token Refresh</div>';
                } else if (isRetryRequest) {
                    html += '<div class="transaction-step">Step 3: Retry Original Request (with new token)</div>';
                }
            }

            // Format the request
            const requestFormatted = HTTPTransaction.formatRequest(
                transaction.request.method,
                transaction.request.url,
                transaction.request.headers,
                transaction.request.body
            );

            // Format the response
            const responseFormatted = HTTPTransaction.formatResponse(
                transaction.response.status,
                transaction.response.statusText,
                transaction.response.headers,
                transaction.response.body
            );

            html += `
                <div class="http-message http-request">
                    <div class="http-message-header">
                        🌐 HTTP Request ${isRefreshRequest ? '(Token Refresh)' : isRetryRequest ? '(Retry with New Token)' : ''}
                    </div>
                    <div class="http-message-content"><pre>${requestFormatted}</pre></div>
                </div>
                <div class="transaction-flow">⬇️</div>
                <div class="http-message ${responseClass}">
                    <div class="http-message-header">
                        📨 HTTP Response ${transaction.response.status === 401 ? '(Token Expired)' : ''}
                    </div>
                    <div class="http-message-content"><pre>${responseFormatted}</pre></div>
                </div>
            `;

            if (index < transactionHistory.length - 1) {
                html += '<div class="transaction-flow">⬇️</div>';
            }
        });

        detailsContainer.innerHTML = html;
        detailsContainer.classList.add('visible');
    }

    // HTTP Transaction Module
    const HTTPTransaction = {
        formatHeaders(headers) {
            let formatted = '';
            if (headers && typeof headers === 'object') {
                Object.entries(headers).forEach(([name, value]) => {
                    if (value) {  // Only show headers with values
                        formatted += `<span class="http-header-name">${name}:</span> <span class="http-header-value">${value}</span>\n`;
                    }
                });
            }
            return formatted;
        },

        formatRequest(method, url, headers, body) {
            const urlObj = new URL(url, window.location.origin);
            let request = `<span class="http-method">${method}</span> ${urlObj.pathname}${urlObj.search} HTTP/1.1\n`;
            request += `<span class="http-header-name">Host:</span> <span class="http-header-value">${urlObj.host}</span>\n`;
            request += this.formatHeaders(headers);
            if (body) {
                request += `\n${body}`;
            }
            return request;
        },

        formatResponse(status, statusText, headers, body) {
            const statusClass = status === 200 ? 'http-status-200' : 'http-status-401';
            let formatted = `HTTP/1.1 <span class="${statusClass}">${status} ${statusText}</span>\n`;
            if (headers) {
                formatted += this.formatHeaders(headers);
            }
            if (body) {
                formatted += `\n${body}`;
            }
            return formatted;
        }
    };

    // JWT Decoder Module
    const JWTDecoder = {
        base64UrlDecode(str) {
            const padding = '='.repeat((4 - str.length % 4) % 4);
            const base64 = (str + padding)
                .replace(/-/g, '+')
                .replace(/_/g, '/');

            try {
                const decoded = atob(base64);
                return decodeURIComponent(escape(decoded));
            } catch (e) {
                console.error('Error decoding base64:', e);
                return null;
            }
        },

        decode(token) {
            if (!token) return null;

            const parts = token.split('.');
            if (parts.length !== 3) {
                console.error('Invalid JWT format');
                return null;
            }

            try {
                const header = this.base64UrlDecode(parts[0]);
                const payload = this.base64UrlDecode(parts[1]);

                return {
                    header: JSON.parse(header),
                    payload: JSON.parse(payload),
                    signature: parts[2],
                    headerRaw: parts[0],
                    payloadRaw: parts[1]
                };
            } catch (e) {
                console.error('Error parsing JWT:', e);
                return null;
            }
        },

        formatTimestamp(timestamp) {
            if (!timestamp) return 'N/A';
            const date = new Date(timestamp * 1000);
            return date.toLocaleString();
        },

        formatJSON(obj, isPayload = false) {
            let formatted = '';
            for (const [key, value] of Object.entries(obj)) {
                formatted += `<div class="jwt-field">`;
                formatted += `<span class="jwt-field-name">${key}:</span> `;

                if (typeof value === 'object' && value !== null) {
                    formatted += `<span class="jwt-field-value">${JSON.stringify(value)}</span>`;
                } else {
                    formatted += `<span class="jwt-field-value">${value}</span>`;
                }

                if (isPayload && ['exp', 'iat', 'nbf'].includes(key) && typeof value === 'number') {
                    formatted += `<span class="jwt-field-timestamp">(${this.formatTimestamp(value)})</span>`;
                }

                formatted += `</div>`;
            }
            return formatted;
        },

        display(token) {
            const decoded = this.decode(token);
            if (!decoded) {
                document.getElementById('jwtDecoded').classList.remove('show');
                return;
            }

            document.getElementById('jwtHeader').innerHTML = this.formatJSON(decoded.header);
            document.getElementById('jwtHeaderRaw').innerHTML = `<strong>Raw:</strong> ${decoded.headerRaw}`;
            document.getElementById('jwtPayload').innerHTML = this.formatJSON(decoded.payload, true);
            document.getElementById('jwtPayloadRaw').innerHTML = `<strong>Raw:</strong> ${decoded.payloadRaw}`;
            document.getElementById('jwtSignature').textContent = decoded.signature;
            document.getElementById('jwtDecoded').classList.add('show');
        },

        hide() {
            document.getElementById('jwtDecoded').classList.remove('show');
        }
    };

    // User card click handlers
    document.addEventListener('DOMContentLoaded', function() {
        const userCards = document.querySelectorAll('.user-card');

        userCards.forEach(card => {
            card.addEventListener('click', function() {
                userCards.forEach(c => c.classList.remove('active'));
                this.classList.add('active');

                const username = this.dataset.username;
                const password = this.dataset.password;

                document.getElementById('username').value = username;
                document.getElementById('password').value = password;

                const inputs = ['username', 'password'];
                inputs.forEach(id => {
                    const input = document.getElementById(id);
                    input.style.transition = 'none';
                    input.style.background = 'linear-gradient(to right, rgba(102, 126, 234, 0.1), transparent)';
                    setTimeout(() => {
                        input.style.transition = 'all 0.3s ease';
                        input.style.background = 'white';
                    }, 100);
                });
            });
        });

        // Set the first user as active by default
        const firstCard = userCards[0];
        if (firstCard) {
            firstCard.click();
        }

        updateAuthStatus();
    });

    // Update UI based on authentication state
    function updateAuthStatus() {
        const statusIndicator = document.getElementById('authStatus');
        const protectedBtn = document.getElementById('protectedBtn');
        const protectedAdminBtn = document.getElementById('protectedAdminBtn');
        const refreshBtn = document.getElementById('refreshBtn');

        if (accessToken) {
            statusIndicator.classList.remove('disconnected');
            statusIndicator.classList.add('connected');
        } else {
            statusIndicator.classList.remove('connected');
            statusIndicator.classList.add('disconnected');
        }
    }

    // Display response in a formatted way
    function displayResponse(elementId, response, isError = false) {
        const element = document.getElementById(elementId);

        const header = element.querySelector('.response-header');
        element.innerHTML = '';
        if (header) {
            element.appendChild(header);
        }

        const textNode = document.createTextNode(response);
        element.appendChild(textNode);

        element.className = `response ${isError ? 'error' : 'success'}`;
        element.classList.add('show');
    }

    // Update token display
    function updateTokenDisplay(tokens) {
        const tokenDisplay = document.getElementById('tokenDisplay');

        if (tokens && tokens.accessToken) {
            accessToken = tokens.accessToken;
            refreshToken = tokens.refreshToken;
            tokenExpiryTime = new Date(Date.now() + (tokens.expiresIn * 1000));
            initialTokenDuration = tokens.expiresIn;

            tokenDisplay.innerHTML = `
                <strong>Access Token:</strong><br>${accessToken}<br><br>
                <strong>Refresh Token:</strong><br>${refreshToken}<br><br>
                <strong>Token Type:</strong> ${tokens.tokenType}<br>
                <strong>Expires In:</strong> ${tokens.expiresIn} seconds<br>
                <strong>Expires At:</strong> ${tokenExpiryTime.toLocaleString()}
            `;
            tokenDisplay.classList.add('has-token');

            JWTDecoder.display(accessToken);
            updateAuthStatus();

            // Start the countdown timer
            TokenTimer.start(tokenExpiryTime, initialTokenDuration);
        } else {
            accessToken = null;
            refreshToken = null;
            tokenExpiryTime = null;
            initialTokenDuration = null;

            tokenDisplay.innerHTML = '<span style="color: #999;">No JWT token yet. Login to receive a token.</span>';
            tokenDisplay.classList.remove('has-token');

            JWTDecoder.hide();
            updateAuthStatus();

            // Hide the timer
            TokenTimer.hide();
        }
    }


    // Login function
    async function login() {
        const username = document.getElementById('username').value;
        const password = document.getElementById('password').value;

        if (!username || !password) {
            displayResponse('authResponse', 'Please enter both username and password', true);
            return;
        }

        try {
            // Clear transaction history for login
            transactionHistory = [];

            const url = '/login';
            const formData = new URLSearchParams();
            formData.append('username', username);
            formData.append('password', password);

            const headers = {
                'Content-Type': 'application/x-www-form-urlencoded'
            };

            const response = await fetch(url, {
                method: 'POST',
                headers: headers,
                body: formData
            });

            const text = await response.text();

            // Log login transaction
            transactionHistory.push({
                request: {
                    method: 'POST',
                    url: url,
                    headers: headers,
                    body: formData.toString()
                },
                response: {
                    status: response.status,
                    statusText: response.statusText,
                    headers: {
                        'Content-Type': response.headers.get('Content-Type') || 'text/plain',
                        'Content-Length': text.length
                    },
                    body: text
                }
            });

            displayCompleteTransactionFlow();

            if (response.ok) {
                const tokens = JSON.parse(text);
                displayResponse('authResponse', `Login successful for: ${username}`);
                updateTokenDisplay(tokens);
            } else {
                displayResponse('authResponse', `Login failed: ${text}`, true);
                updateTokenDisplay(null);
            }
        } catch (error) {
            displayResponse('authResponse', `Network error: ${error.message}`, true);
        }
    }

    // Access protected route using makeAuthenticatedRequest
    async function accessProtectedRoute() {
        try {
            const response = await makeAuthenticatedRequest('/profile/me');
            const text = await response.text();

            if (response.ok) {
                displayResponse('protectedResponse', text);
            } else {
                displayResponse('protectedResponse', `Error: ${text}`, true);
            }
        } catch (error) {
            displayResponse('protectedResponse', error.message, true);
        }
    }

    // Access admin-only route to list all users
    async function accessAdminRoute() {
        try {
            const response = await makeAuthenticatedRequest('/admin/users');
            const text = await response.text();

            if (response.ok) {
                displayResponse('protectedResponse', text);
            } else {
                displayResponse('protectedResponse', `Admin Access Error: ${text}`, true);
            }
        } catch (error) {
            displayResponse('protectedResponse', error.message, true);
        }
    }

    // Manual refresh tokens
    async function refreshTokensManually() {
        if (!refreshToken) {
            displayResponse('protectedResponse', 'No refresh token available. Please login first.', true);
            return;
        }

        try {
            // Clear transaction history for manual refresh
            transactionHistory = [];

            const url = '/refresh';
            const formData = new URLSearchParams();
            formData.append('refreshToken', refreshToken);

            const headers = {
                'Content-Type': 'application/x-www-form-urlencoded'
            };

            const response = await fetch(url, {
                method: 'POST',
                headers: headers,
                body: formData
            });

            const text = await response.text();

            // Log refresh transaction
            transactionHistory.push({
                request: {
                    method: 'POST',
                    url: url,
                    headers: headers,
                    body: formData.toString()
                },
                response: {
                    status: response.status,
                    statusText: response.statusText,
                    headers: {
                        'Content-Type': response.headers.get('Content-Type') || 'text/plain',
                        'Content-Length': text.length
                    },
                    body: text
                }
            });

            displayCompleteTransactionFlow();

            if (response.ok) {
                const tokens = JSON.parse(text);
                displayResponse('protectedResponse', 'Tokens refreshed successfully!');
                updateTokenDisplay(tokens);
            } else {
                displayResponse('protectedResponse', `Token refresh failed: ${text}`, true);
                clearTokens();
            }
        } catch (error) {
            displayResponse('protectedResponse', `Network error: ${error.message}`, true);
        }
    }

    // Logout and revoke refresh token
    async function revokeRefreshToken() {
        if (!refreshToken) {
            displayResponse('authResponse', 'No refresh token available. Please login first.', true);
            return;
        }

        try {
            // Clear transaction history for logout
            transactionHistory = [];

            const url = '/logout';
            const formData = new URLSearchParams();
            formData.append('refreshToken', refreshToken);

            const headers = {
                'Content-Type': 'application/x-www-form-urlencoded'
            };

            const response = await fetch(url, {
                method: 'POST',
                headers: headers,
                body: formData
            });

            const text = await response.text();

            // Log logout transaction
            transactionHistory.push({
                request: {
                    method: 'POST',
                    url: url,
                    headers: headers,
                    body: formData.toString()
                },
                response: {
                    status: response.status,
                    statusText: response.statusText,
                    headers: {
                        'Content-Type': response.headers.get('Content-Type') || 'text/plain',
                        'Content-Length': text.length
                    },
                    body: text
                }
            });

            displayCompleteTransactionFlow();

            if (response.ok) {
                displayResponse('authResponse', text);
                clearTokens();
            } else {
                displayResponse('authResponse', `Logout failed: ${text}`, true);
            }
        } catch (error) {
            displayResponse('authResponse', `Network error: ${error.message}`, true);
        }
    }

    // Clear tokens
    function clearTokens() {
        updateTokenDisplay(null);
        document.getElementById('authResponse').style.display = 'none';
        document.getElementById('protectedResponse').style.display = 'none';
    }

    // Clear transactions
    function clearTransactions() {
        transactionHistory = [];
        const detailsContainer = document.getElementById('transactionDetails');
        if (detailsContainer) {
            detailsContainer.classList.remove('visible');
            setTimeout(() => {
                detailsContainer.innerHTML = `
                    <p style="text-align: center; color: #6c757d; font-style: italic; padding: 20px;">
                        Click any endpoint button to see detailed HTTP request/response messages
                    </p>
                `;
            }, 300);
        }
    }

    document.getElementById('username').addEventListener('keypress', function(e) {
        if (e.key === 'Enter') {
            document.getElementById('password').focus();
        }
    });

    document.getElementById('password').addEventListener('keypress', function(e) {
        if (e.key === 'Enter') {
            login();
        }
    });
</script>
</body>
</html>
